#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Author: leuelcey (wyd)
# E-mail: leuelcey@163.com
# Creation date: 2021.4
# Modified date: 2021.4

import sqlite3 as DB
from basicdata import BasicData

class Bagua(object):
    """
    八卦类。
    """
    __slots__ = ('__dict__', '__weakref__', 'bgbd', 'bagua', 'gans', 'zhis', 'fang', 'zhyi', 'najia', 'nazhi')

    def __init__(self):
        super(Bagua, self).__init__()
        self.bgbd = BasicData()
        self.zhyi = Zhouyi()  # 周易类实例化
        self.bagua = self.bgbd.get_constant(section='yijing', varname='bagua')
        self.gans = self.bgbd.get_constant(section='bazi', varname='gan')
        self.zhis = self.bgbd.get_constant(section='bazi', varname='zhi')
        self.najia = self.bgbd.get_constant(section='yijing', varname='najia')
        self.nazhi = self.bgbd.get_constant(section='yijing', varname='nazhi')
        self.fang = self.bgbd.get_constant(section='yijing', varname='fangwei')['houtian']

    # 取日名对应的数
    def Day_Value(self, ri=''):
        """
        取农历日名对应的日数，如初一对应1、廿八对应28...
        用法：rs = Day_Value([农历日名])
        :param ri: 农历日名
        :return: 对应的日数
        """
        if not ri:
            return 0
        en_num = {'零': 0, '〇': 0, '一': 1, '二': 2, '三': 3, '四': 4, '五': 5, '六': 6, '七': 7, '八': 8, '九': 9, '十': 10, '廿': 20, '卅': 30}
        if ri[1] == '十':
            if ri[0] == '初':
                numb = 1
            else:
                numb = en_num[ri[0]]
            return numb * en_num[ri[1]]
        if ri[0] == '初':
            numb = 0
        else:
            numb = en_num[ri[0]]
        return numb + en_num[ri[1]]

    # 取月名对应的数
    def Month_Value(self, yue=''):
        """
        取农历月名对应的月数，如正对应1、冬对应11、腊对应12...
        用法：ms = Month_Value([农历月名])
        :param yue: 农历月名
        :return: 对应的月数
        """
        if not yue:
            return 0
        en_num = {'正': 1, '二': 2, '三': 3, '四': 4, '五': 5, '六': 6, '七': 7, '八': 8, '九': 9, '十': 10, '冬': 11, '十一': 11, '腊': 12, '十二': 12, '拾贰': 12}
        if len(yue) >= 2:
            if yue[:2] not in en_num:
                return 0
            return en_num[yue[:2]]
        if yue[0] not in en_num:
            return 0
        return en_num[yue[0]]

    # 取天干对应的值
    def gan_Value(self, gan=''):
        if gan not in self.gans:
            return 0
        return self.gans.index(gan) + 1

    # 取地支对应的值
    def zhi_Value(self, zhi=''):
        """
        取地支对应的值。
        :param zhi: 地支名
        :return: 地支值
        """
        if zhi not in self.zhis:
            return 0
        return self.zhis.index(zhi) + 1

    # 取八卦卦序
    def q8gua_xuhao(self, gua=''):
        if not gua:
            return 0
        for k, g in enumerate(self.bagua):
            if g['gua'] == gua:
                return k

    # 根据各爻查找卦
    def q8gua_By_yaos(self, yao_xing=''):
        """
        取八卦卦名、卦符。
        用法：g8 = q8gua_By_yaos([爻形])
        :param yao_xing: 爻形，比如`996`为兑卦...
        :return: (`八卦名`, `八卦符`)
        """
        if not yao_xing:
            return ('', '')
        for _, bg in enumerate(self.bagua):
            if bg['yao'] == yao_xing:
                return (bg['gua'], bg['xiang'])
        return ('','')

    # 起卦方法一：连山易，以年月日时对应的数计算。
    def qg_lianshan(self, qglx=0, nian='', yue='', ri='', shi=''):
        """
        连山易取卦法。
        根据年干或支，农历月、日，时辰地支计算。使用年干计算时，是为终身卦，其余为普通卦。
        :param qglx: 起卦类型，0为终身、1为普通
        :param nian: 干支年
        :param yue: 农历月名
        :param ri: 农历日名
        :param shi: 时辰干支
        :return: 卦信息对象
        """
        if qglx not in (0, 1) or not nian or not yue or not ri or not shi:
            return {}
        n_zhi = nian[qglx]
        n_shu = self.gan_Value(n_zhi) if qglx == 0 else self.zhi_Value(n_zhi)
        sg_zhi = (n_shu + self.Month_Value(yue) + self.Day_Value(ri)) % 8
        xg_zhi = (n_shu + self.Month_Value(yue) + self.Day_Value(ri) + self.zhi_Value(shi[1])) % 8
        dy_zhi = (n_shu + self.Month_Value(yue) + self.Day_Value(ri) + self.zhi_Value(shi[1])) % 6
        dy_zhi = 6 if dy_zhi == 0 else dy_zhi

        return self._hq_gua_xinxi(sg_zhi, xg_zhi, dy_zhi)

    # 起卦方法二：先天数，随意报几个数，结合时辰计算。
    def qg_xiantian(self, shi='', *shu):
        """
        先天数取卦法。
        随意报几个数字，结合时辰进行计算。
        :param shi: 时辰干支
        :param shu: 数字清单
        :return: 卦信息对象
        """
        if not shu or (len(shu) == 1 and not shi):
            return {}
        if len(shu) == 1:
            sg_zhi = shu[0] if shu[0] < 8 else shu[0] % 8
            xg_zhi = self.zhi_Value(shi[-1]) if self.zhi_Value(shi[-1]) < 8 else self.zhi_Value(shi[-1]) % 8
            dy_zhi = (shu[0] + self.zhi_Value(shi[-1])) % 6
        elif len(shu) <= 3:
            sg_zhi = shu[0] if shu[0] < 8 else shu[0] % 8
            xg_zhi = shu[1] if shu[1] < 8 else shu[1] % 8
            dy_zhi = sum(shu) % 6
        else:
            qp = int(len(shu) / 2)
            sg_zhi = sum(shu[:qp]) if sum(shu[:qp]) < 8 else sum(shu[:qp]) % 8
            xg_zhi = sum(shu[qp:]) if sum(shu[qp:]) < 8 else sum(shu[qp:]) % 8
            dy_zhi = sum(shu) % 6
        dy_zhi = 6 if dy_zhi == 0 else dy_zhi

        return self._hq_gua_xinxi(sg_zhi, xg_zhi, dy_zhi)

    # 起卦方法三：八卦纳甲法(天地数)，以日干为上卦、时支为下卦。
    def qg_najia(self, nzlx=0, jiri='', shi=''):
        """
        八卦纳甲法(天地数)起卦法。
        日干为上卦、时支为下卦。
        :param nzlx: 纳支类型，0采用纳支，1按地支顺序
        :param jiri: 日干支
        :param shi: 时干支
        :return: 卦信息对象
        """
        if nzlx not in (0, 1) or not jiri or not shi:
            return {}
        nj_zhi = self.gan_Value(jiri[0])
        sg_zhi = self.q8gua_xuhao(self.najia[nj_zhi])
        nz_idx = self.zhi_Value(shi[1])
        if nzlx == 0:
            xg_zhi = self.q8gua_xuhao(self.nazhi[nz_idx])
            nz_zhi = self.najia[1:9].index(self.nazhi[nz_idx])
        else:
            xg_zhi = nz_idx % 8
            nz_zhi = nz_idx
        dy_zhi = (nj_zhi + nz_zhi) % 6
        dy_zhi = 6 if dy_zhi == 0 else dy_zhi

        return self._hq_gua_xinxi(sg_zhi, xg_zhi, dy_zhi)

    # 起卦方法四：后天象位法，人或物的象为上卦、方位为下卦。
    def qg_xiangwei(self, sg_xng='', xg_wei=''):
        """
        后天象位起卦法。
        以人或物所取之象为上卦、方位为下卦。
        :param sg_xng: 上卦卦象
        :param xg_wei: 方位名
        :return: 卦信息对象
        """
        if not sg_xng or not xg_wei or (xg_wei not in self.fang):
            return {}
        sg_zhi = self.q8gua_xuhao(sg_xng)
        sg_shu = 8 if sg_zhi == 0 else sg_zhi
        xg_zhi = self.q8gua_xuhao(self.fang[xg_wei])
        xg_shu = 8 if xg_zhi == 0 else xg_zhi
        dy_zhi = (sg_shu + xg_shu) % 6
        dy_zhi = 6 if dy_zhi == 0 else dy_zhi

        return self._hq_gua_xinxi(sg_zhi, xg_zhi, dy_zhi)

    # 起卦方法五：人物象及动作取卦。
    def qg_dongzuo(self, sg_xng='', xg_xng=''):
        """
        以人物前后两个动作之象取卦。
        :param sg_xng: 上卦卦象
        :param xg_xng: 下卦卦象
        :return: 卦信息对象
        """
        if not sg_xng or not xg_xng:
            return {}
        sg_zhi = self.q8gua_xuhao(sg_xng)
        xg_zhi = self.q8gua_xuhao(xg_xng)
        dy_zhi = ((8 if sg_zhi == 0 else sg_zhi) + (8 if xg_zhi == 0 else xg_zhi)) % 6
        dy_zhi = 6 if dy_zhi == 0 else dy_zhi

        return self._hq_gua_xinxi(sg_zhi, xg_zhi, dy_zhi)

    # 取卦信息
    def _hq_gua_xinxi(self, shgua, xagua, biyao):
        be_gua = (self.bagua[shgua]['gua'], self.bagua[xagua]['gua'])
        be_xng = (self.bagua[shgua]['xiang'], self.bagua[xagua]['xiang'])
        bi_gua, bi_xng = self._hq_biangua(self.bagua[shgua], self.bagua[xagua], biyao)
        begw, beyw = self._hq_guawen(be_gua[0], be_gua[1], biyao)
        bigw, biyw = self._hq_guawen(bi_gua[0], bi_gua[1], biyao)
        gua = {'ben_gua': {'mings': be_gua, 'xings': be_xng, 'gua_ci': begw, 'yao_ci': beyw},
                    'bian_gua': {'mings': bi_gua, 'xings': bi_xng, 'gua_ci': bigw, 'yao_ci': biyw},
                    'bian_yao': biyao
                    }
        return gua

    # 取变卦
    def _hq_biangua(self, ys_gua, yx_gua, bg_yao):
        if bg_yao <= 3:
            if bg_yao == 1:
                y = ('9' if yx_gua['yao'][0] == '6' else '6') + yx_gua['yao'][1:]
            elif bg_yao == 3:
                y = yx_gua['yao'][:2] + ('9' if yx_gua['yao'][-1] == '6' else '6')
            else:
                y = yx_gua['yao'][0] + ('9' if yx_gua['yao'][1] == '6' else '6') + yx_gua['yao'][-1]
            return (ys_gua['gua'], self.q8gua_By_yaos(y)[0]), (ys_gua['xiang'], self.q8gua_By_yaos(y)[1])
        else:
            if bg_yao == 4:
                y = ('9' if ys_gua['yao'][0] == '6' else '6') + ys_gua['yao'][1:]
            elif bg_yao == 6:
                y = ys_gua['yao'][:2] + ('9' if ys_gua['yao'][-1] == '6' else '6')
            else:
                y = ys_gua['yao'][0] + ('9' if ys_gua['yao'][1] == '6' else '6') + ys_gua['yao'][-1]
            return (self.q8gua_By_yaos(y)[0], yx_gua['gua']), (self.q8gua_By_yaos(y)[1], yx_gua['xiang'])

    # 取卦辞
    def _hq_guawen(self, s_gua, x_gua, d_yao):
        gx = f'{s_gua}上{x_gua}下'
        gc = self.zhyi.guaci_By_xing('gua_hao', 'gua_ming', 'gua_xu', 'gua_jue', 'gua_wen', 'gua_tuan', 'gua_xiang', gua_xing=gx)
        if gc:
            xx = gc[0]
            gwen = {'ming': xx[1], 'xu': xx[2], 'jue': xx[3], 'wen': xx[4], 'tuan': xx[5], 'xiang': xx[6]}
            yc = self.zhyi.yao_ci('yao_wen', 'yao_xiang', gua_hao=xx[0], yao_hao=d_yao)
            if yc:
                xx = yc[0]
                ywen = {'wen': xx[0], 'xiang': xx[1]}
            else:
                ywen = {}
        else:
            gwen = {}
            ywen = {}
        return gwen, ywen


class Zhouyi(object):
    """
    周易类。
    """
    __slots__ = ('__dict__', '__weakref__', 'file', 'db', 'cs', 'sql')

    def __init__(self):
        super(Zhouyi, self).__init__()
        self.file = 'datas/zhouyi.db'
        self.db = DB.connect(self.file)
        self.cs = self.db.cursor()
        self.sql = ''

    def __del__(self):
        if self.cs:
            self.cs.close()
        if self.db:
            self.db.close()

    # 取爻辞，当卦号为0时取全部卦的某爻；当爻号为0时取取全部爻辞。
    def yao_ci(self, *fields, gua_hao=0, yao_hao=0):
        """
        取爻辞。
        用法：yres = yao_ci(字段清单, [卦号][爻号])
        :param fields: 字段清单，为空时取全部字段
        :param gua_hao: 卦号，默认为0，取所有卦
        :param yao_hao: 爻号，默认为0,取所有爻
        :return: 爻辞记录集
        """
        self.sql = 'SELECT %s FROM yi_yao'
        if not fields:
            zd_list = '*'
        else:
            zd_list = ','.join(fields)
        self.sql = self.sql % zd_list

        condition = f' WHERE gua_hao = {gua_hao}' if gua_hao != 0 else ''
        if condition and yao_hao != 0:
            condition += ' AND'
        elif yao_hao != 0:
            condition = ' WHERE'
        condition = condition + (f' yao_hao = {yao_hao}' if yao_hao != 0 else '')
        self.sql = self.sql + condition

        try:
            self.cs.execute(self.sql)
            return self.cs.fetchall()
        except:
            return []

    # 根据卦号取卦辞，当卦号为0时取全部。
    def guaci_By_hao(self, *fields, gua_hao=0):
        """
        根据卦号取卦辞。
        用法：gres = guaci_By_hao(字段清单, [卦号])
        :param fields: 字段清单，为空时取全部字段
        :param gua_hao: 卦号，默认为0，取全部卦
        :return: 卦辞记录集
        """
        self.sql = 'SELECT %s FROM yi_gua'
        if not fields:
            zd_list = '*'
        else:
            zd_list = ','.join(fields)
        self.sql = self.sql % zd_list
        self.sql = self.sql + (f' WHERE gua_hao = {gua_hao}' if gua_hao != 0 else '')

        try:
            self.cs.execute(self.sql)
            return self.cs.fetchall()
        except:
            return []

    # 根据卦形取卦辞，当卦形为空时取全部。
    def guaci_By_xing(self, *fields, gua_xing=''):
        """
        根据卦行取卦辞。
        用法：gres = guaci_By_xing(字段清单, [卦形])
        :param fields: 字段清单，为空时取全部字段
        :param gua_xing: 卦形，如`离上艮下`，为空时取全部卦
        :return: 卦辞记录集
        """
        self.sql = 'SELECT %s FROM yi_gua'
        if not fields:
            zd_list = '*'
        else:
            zd_list = ','.join(fields)
        self.sql = self.sql % zd_list
        self.sql = self.sql + (f' WHERE gua_xing = "{gua_xing}"' if gua_xing else '')

        try:
            self.cs.execute(self.sql)
            return self.cs.fetchall()
        except:
            return []
